// $Id: tcp.c 84 2011-04-24 09:23:49Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

#include "nio.h"

#include "log.h"
#include "socket.h"
#include "buffer.h"
#include "future.h"
#include "channel.h"

#include "tcp.h"

struct niohandler niotcp_handler = {
		niotcp_handler_setup,
		niotcp_handler_teardown,
		niotcp_handler_handle_upstream,
		niotcp_handler_handle_downstream,
		niotcp_handler_free,
};

struct niotcp_handler *niotcp_handler_new(struct nioreactor *reactor,
		struct timeval *timeout)
{
	struct niotcp_handler *handler;

	niodebug("constructing");

	if (!(handler = calloc(1, sizeof(*handler)))) {
		nioerror("failed to calloc");
		goto exception;
	}

	if (!(handler->future = niofuture_new(reactor))) {
		nioerror("failed to create future");
		goto exception;
	}

	if (timeout) {
		memcpy(&handler->tv, timeout, sizeof(handler->tv));
		handler->timeout = &handler->tv;
	}

	return handler;

exception:
	niotcp_handler_free(handler);
	return NULL;
}

void niotcp_handler_free(void *obj)
{
	struct niotcp_handler *handler = obj;

	niodebug("destructing: %p", handler);

	if (!handler) {
		return;
	}

	if (handler->future) {
		niofuture_free(handler->future);
	}

	free(handler);
}

int niotcp_handler_setup(void *obj, const char *name)
{
	int rc;
	struct niotcp_handler *handler = obj;

	if (!handler) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("setting up: %s", name);

	handler->name = name;
	rc = 0;

finally:
	return rc;
}

int niotcp_handler_teardown(void *obj)
{
	int rc;
	struct niotcp_handler *handler = obj;

	if (!handler) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("tearing down: %s", handler->name);

	handler->upstream = handler->downstream = NULL;
	niofuture_cancel(handler->future);
	rc = 0;

finally:
	return rc;
}

int niotcp_handler_handle_upstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niotcp_handler *handler = obj;

	if (!handler || !message || !channel) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("handling upstream: %s", handler->name);

	if (handler->upstream) {
		nioerror("upstream handing in progress");
		rc = NIO_EINPROGRESS;
		goto finally;
	}

	handler->channel = channel;
	handler->upstream = message;

	niofuture_cancel(handler->future);

	niodebug("listening %s:%d readable timeout=%ld.%06ld",
			inet_ntoa(channel->socket->address),
			channel->socket->port,
			handler->tv.tv_sec,
			handler->tv.tv_usec);

	if ((rc = niofuture_readable(handler->future,
			channel->socket, handler->timeout,
			niotcp_handler_on_readable, handler))) {
		nioerror("failed to listen readable: %s",
				nio_strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niotcp_handler_handle_downstream(void *obj,
		struct niochannel *channel, void *message)
{
	int rc;
	struct niotcp_handler *handler = obj;

	if (!handler || !channel || !message) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("handling downstream: %s", handler->name);

	handler->channel = channel;

	if (handler->downstream) {
		nioerror("downstream message pending");
		rc = NIO_EINPROGRESS;
		goto finally;
	} else {
		handler->downstream = message;
	}

//	if (niobuffer_position(handler->buffer) > 0) { // read more
//		if ((rc = niofuture_readable(handler->future,
//				channel->socket, handler->timeout,
//				niotcp_handler_on_readable, handler))) {
//			nioerror("failed to listen readable: %s",
//					nio_strerror(rc));
//			goto finally;
//		}
//	} else { // ready to write
	niofuture_cancel(handler->future);

	niodebug("listening %s:%d writable (timeout=%ld.%06ld): "
			"pos=%d, limit=%d",
			inet_ntoa(channel->socket->address),
			channel->socket->port,
			handler->tv.tv_sec,
			handler->tv.tv_usec,
			niobuffer_position(handler->downstream),
			niobuffer_limit(handler->downstream));

	if ((rc = niofuture_writable(handler->future,
			channel->socket, handler->timeout,
			niotcp_handler_on_writable, handler))) {
		nioerror("failed to listen writable: %s",
				nio_strerror(rc));
		goto finally;
	}
//	}

	rc = 0;

finally:
	return rc;
}

int niotcp_handler_read(struct niotcp_handler *handler)
{
	int rc;
	struct niochannel *channel = handler->channel;
	struct niobuffer *buffer = handler->upstream;

	niodebug("reading channel %s:%d", inet_ntoa(channel->socket->address),
			channel->socket->port);

	if ((rc = niobuffer_read(buffer, channel->socket))) {
		if ((rc == NIO_EAGAIN)) {
			niodebug("listening %s:%d readable, timeout=%ld.%06ld",
					inet_ntoa(channel->socket->address),
					channel->socket->port,
					handler->tv.tv_sec,
					handler->tv.tv_usec);

			if ((rc = niofuture_readable(handler->future,
					channel->socket, handler->timeout,
					niotcp_handler_on_readable, handler))) {
				nioerror("failed to listen readable: %s",
						nio_strerror(rc));
				goto finally;
			}

			rc = 0;
		} else {
			nioerror("failed to read channel: %s",
					nio_strerror(rc));
		}
		goto finally;
	}

	niobuffer_flip(buffer);

	if ((rc = niochannel_send_upstream(channel, handler->name, buffer))) {
		nioerror("failed to send upstream: %s", nio_strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niotcp_handler_write(struct niotcp_handler *handler)
{
	int rc;
	struct niochannel *channel = handler->channel;
	struct niobuffer *buffer = handler->downstream;

	niodebug("writing channel %s:%d", inet_ntoa(channel->socket->address),
			channel->socket->port);

	if ((rc = niobuffer_write(buffer, channel->socket))) {
		if ((rc == NIO_EAGAIN)) {
			niodebug("listening %s:%d writable, timeout=%ld.%06ld",
					inet_ntoa(channel->socket->address),
					channel->socket->port,
					handler->tv.tv_sec,
					handler->tv.tv_usec);
			if ((rc = niofuture_writable(handler->future,
					channel->socket, handler->timeout,
					niotcp_handler_on_writable, handler))) {
				nioerror("failed to listen writable: %s",
						nio_strerror(rc));
				goto finally;
			}

			rc = 0;
		} else {
			nioerror("failed to write channel: %s",
					nio_strerror(rc));
		}
		goto finally;
	}

	if (niobuffer_remaining(buffer) > 0) {
		if ((rc = niotcp_handler_write(handler))) {
			nioerror("failed to write channel: %s",
					nio_strerror(rc));
			goto finally;
		}
	} else {
//		niobuffer_clear(handler->buffer);
//
//		if ((rc = niotcp_handler_read(handler))) {
//			nioerror("failed to read channel: %s",
//					nio_strerror(rc));
//			goto finally;
//		}
		if ((rc = niochannel_send_downstream(
				handler->channel, handler->name,
				handler->downstream))) {
			nioerror("failed to send down stream: %s",
					nio_strerror(rc));
			goto finally;
		}
	}

	rc = 0;

finally:
	return rc;
}

void niotcp_handler_on_readable(struct niofuture *future,
		struct niosocket *socket, int flags, void *cbarg)
{
	int rc;
	struct niotcp_handler *handler = cbarg;

	niodebug("handling %s:%d readable",
			inet_ntoa(handler->channel->socket->address),
			handler->channel->socket->port);

	if ((rc = niotcp_handler_read(handler))) {
		nioerror("failed to read channel: %s", nio_strerror(rc));
		goto finally;
	}

finally:
	if (rc != 0) {
		niochannel_send_downstream(handler->channel, handler->name,
				NULL);
	}
	return;
}

void niotcp_handler_on_writable(struct niofuture *future,
		struct niosocket *socket, int flags, void *cbarg)
{
	int rc;
	struct niotcp_handler *handler = cbarg;

	niodebug("handling %s:%d writable",
			inet_ntoa(handler->channel->socket->address),
			handler->channel->socket->port);

	if ((rc = niotcp_handler_write(handler))) {
		nioerror("failed to write channel: %s", nio_strerror(rc));
		goto finally;
	}

finally:
	if (rc != 0) {
		niochannel_send_downstream(handler->channel, handler->name,
				NULL);
	}
	return;
}
